package elastic

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/shijun123456/kcgin"
	"github.com/elastic/go-elasticsearch"
	"github.com/elastic/go-elasticsearch/esapi"
	"io"
	"log"
	"math"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

var (
	// 单次搜索最大返回数量,防止陷入深度搜索，拖垮搜索服务器
	maxSearchNum = 2000
	// 自定义分析器
	analyzer = []string{"standard", "ik_smart", "ik_max_word"} // standard=es默认分析器,对中文支持较差
	// ik_smart=插件，中文分析器,对中文分词支持优秀 ik_max_word=插件，加强版ik_smart,对短语分词更细腻
	indexPrefix = "kc-"
	// 每个索引的分片数
	numShareds = 3
	// 每个索引每个分片对应的副本数
	numReplicas = 1

	client   *elasticsearch.Client
	indexPre string
)

// 定义结构体
type ElasticClient struct {
	// 索引名称
	index string
	// 索引类型
	indexType string
	// 聚合筛选条件
	term []map[string]map[string]interface{}
	// 聚合筛选条件In
	terms []map[string]map[string]interface{}
	// 聚合排除筛选条件
	mustNot []map[string]map[string]interface{}
	//聚合筛选条件Or
	should []map[string]map[string]interface{}

	// 范围筛选条件
	ranges []map[string]map[string]interface{}
	// 文本筛选
	keyword []map[string]map[string]interface{}
	// 排序
	sort []map[string]interface{}
	// 分页
	limit map[string]int
	// 地理位置
	geo map[string]interface{}
	// 欢迎度
	factor map[string]interface{}
	// 打印请求json
	lastQuery string
	// es request  client
	client *elasticsearch.Client
	// es response client
	response *esapi.Response
}

// 创建索引
//要求手动创建
//func (e *ElasticClient) CreateIndex(params ...interface{}) bool {
//	body := make(map[string]interface{})
//	body["settings"] = map[string]interface{}{
//		"number_of_shards":numShareds,
//		"number_of_replicas":numReplicas,
//		"analysis":map[string]interface{}{
//			"analyzer":map[string]interface{}{
//				"my_analyzer":map[string]interface{}{
//					"tokenizer":analyzer[2],
//				},
//			},
//		},
//	}
//	var location bool
//	if len(params) >0{
//		location = params[0].(bool)
//	}
//	if location{
//		body["mappings"] = map[string]interface{}{
//			"properties":map[string]interface{}{
//				"location":map[string]interface{}{
//					"type":"geo_point",
//				},
//			},
//		}
//	}
//	jsonBody, _ := json.Marshal(body)
//	req := esapi.IndicesCreateRequest{
//		Index: e.index,
//		Body:  bytes.NewReader(jsonBody),
//	}
//	res, err := req.Do(context.Background(), e.client)
//	if err != nil{
//		fmt.Println("req.Do Error,Error is ",err.Error())
//		return false
//	}
//	e.response = res
//	defer  e.close()
//	var maps = make(map[string]interface{})
//	responseStr := res.String()[strings.Index(res.String(),"{"):]
//	err = json.Unmarshal([]byte(responseStr),&maps)
//	if maps["acknowledged"] == true{
//		return true
//	}else{
//		fmt.Println("req.Do Error,Error is",maps["error"])
//		return false
//	}
//}

func init() {
	hostAddr := kcgin.AppConfig.String("es.hostaddr")
	indexPre = kcgin.AppConfig.String("es.indexpre")
	username := kcgin.AppConfig.String("es.username")
	password := kcgin.AppConfig.String("es.password")

	var err error
	if hostAddr == "" {
		err = errors.New("es hostaddr has no config")
		log.Panic(err)
	}
	if indexPre == "" {
		indexPre = indexPrefix
	}
	cfg := elasticsearch.Config{
		Addresses: []string{hostAddr},
		Transport: &http.Transport{
			MaxIdleConnsPerHost:   10,
			ResponseHeaderTimeout: time.Second * 5,
			Proxy: func(req *http.Request) (*url.URL, error) {
				if username != "" && password != "" {
					req.SetBasicAuth(username, password)
				}
				return nil, nil
			},
			DialContext: (&net.Dialer{Timeout: time.Second}).DialContext,
			TLSClientConfig: &tls.Config{
				MinVersion: tls.VersionTLS11,
			},
		},
	}
	client, err = elasticsearch.NewClient(cfg)
	if err != nil {
		err = errors.New(fmt.Sprintf("es NewCient error,error is %s", err.Error()))
		log.Panic(err)
	}
}
func (e *ElasticClient) arrayData(name string, condition string, v interface{}, source *string, params *map[string]interface{}) {
	switch strings.ToLower(condition) {
	case "inc":
	case "+":
		*source += "ctx._source." + name + "=ctx._source." + name + "+params." + name + ";"
		break
	case "dec":
	case "-":
		*source += "ctx._source." + name + "=ctx._source." + name + "-params." + name + ";"
		break
	}
	(*params)[name] = v
}

// 更新指定文档字段值
// @param string docId           文档id
// @data map[string]interface    更新内容 map[string]interface{}{"fieldeName":"value","fieldName1":[]interface{"+",1}} 可自增自减
func (e *ElasticClient) UpdateById(docId string, data map[string]interface{}) bool {
	source := ""
	params := make(map[string]interface{})
	for i, v := range data {
		if k, ok := v.([]interface{}); ok {
			if len(k) == 2 {
				e.arrayData(i, k[0].(string), k[1], &source, &params)
			}
		} else if k, ok := v.([]string); ok {
			if len(k) == 2 {
				e.arrayData(i, k[0], k[1], &source, &params)
			}
		} else {
			source += "ctx._source." + i + "=" + "params." + i + ";"
			params[i] = v
		}
	}
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"source": source,
			"params": params,
		},
	}
	jsonBody, _ := json.Marshal(body)
	res, err := e.client.Update(e.index, docId, bytes.NewReader(jsonBody))
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

// 根据过滤器条件更新文档
// @data map[string]interface    更新内容 map[string]interface{}{"fieldeName":"value","fieldName1":[]interface{"+",1}} 可自增自减
func (e *ElasticClient) Update(data map[string]interface{}) bool {
	source := ""
	params := make(map[string]interface{})
	for i, v := range data {
		if k, ok := v.([]interface{}); ok {
			if len(k) == 2 {
				e.arrayData(i, k[0].(string), k[1], &source, &params)
			}
		} else if k, ok := v.([]string); ok {
			if len(k) == 2 {
				e.arrayData(i, k[0], k[1], &source, &params)
			}
		} else {
			source += "ctx._source." + i + "=" + "params." + i + ";"
			params[i] = v
		}
	}
	search, capBool := e.getSearch()
	if capBool == false {
		// 无条件禁止更新
		return false
	}

	body := map[string]interface{}{
		"query": search["query"],
		"script": map[string]interface{}{
			"source": source,
			"params": params,
		},
	}
	jsonBody, _ := json.Marshal(body)
	req := esapi.UpdateByQueryRequest{
		Index:        []string{e.index},
		DocumentType: []string{e.indexType},
		Body:         bytes.NewReader(jsonBody),
	}
	res, err := req.Do(context.Background(), e.client)
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

// 重置索引文档某个字段默认值
// @param  string      docField    字段名称
// @param  interface{} value       设置值
// @return bool
func (e *ElasticClient) ResetFieldValue(docField string, value interface{}) bool {
	var s interface{}
	if vi, ok := value.(int); ok {
		s = strconv.Itoa(vi)
	} else {
		s = value.(string)
	}
	inline := fmt.Sprintf("if(ctx._source.%s == null){ctx._source.%s=%s}", docField, docField, s)
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"inline": inline,
		},
	}
	jsonBody, _ := json.Marshal(body)
	req := esapi.UpdateByQueryRequest{
		Index: []string{e.index},
		Body:  bytes.NewReader(jsonBody),
	}
	res, err := req.Do(context.Background(), e.client)
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	defer e.close()
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

// 新增/更新一个文档
// @param  interface{} id 文档ID
// @param  map[string]interface{}   文档数据
// @return bool
func (e *ElasticClient) PostDoc(id interface{}, data map[string]interface{}) bool {
	// 如果为整型则转字符串类型
	if vs, p := id.(int); p {
		id = strconv.Itoa(vs)
	}
	idF := id.(string)
	jsonBody, _ := json.Marshal(data)
	req := esapi.IndexRequest{
		Index:        e.index,
		DocumentType: e.indexType,
		DocumentID:   idF,
		Body:         bytes.NewReader(jsonBody),
	}
	res, err := req.Do(context.Background(), e.client)
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	defer e.close()
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if maps["result"] != "" {
		return true
	} else {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	}
}

// 删除文档
// @param  interface{} id 文档ID
// @return bool
func (e *ElasticClient) DeleteDoc(id interface{}) bool {
	// 如果为整型则转字符串类型
	if vs, p := id.(int); p {
		id = strconv.Itoa(vs)
	}
	idF := id.(string)
	req := esapi.DeleteRequest{
		Index:        e.index,
		DocumentType: e.indexType,
		DocumentID:   idF,
	}
	res, err := req.Do(context.Background(), e.client)
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	defer e.close()
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if maps["result"] == "deleted" {
		return true
	} else {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	}
}

// 文档字段值精确值筛选
// @param  string      docField 字段名
// @param  interface{} value    字段值 类型可以是整型、字符串
// @return this
func (e *ElasticClient) SetFilter(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"term": {docField: value},
	}
	if len(e.term) == 0 {
		e.term = make([]map[string]map[string]interface{}, 0)
		e.term = append(e.term, maps)
	} else {
		e.term = append(e.term, maps)
	}
	return e
}

// 文档字段值数组精确值筛选
// @param  string      docField 字段名
// @param  interface{} value    数组,查找多个精确值，使用数组形式 []int or []string
// @return this
func (e *ElasticClient) SetFilterArray(docField string, value interface{}) *ElasticClient {
	if len(e.term) == 0 {
		e.term = make([]map[string]map[string]interface{}, 0)
	}
	if val, ok := value.([]string); ok {
		for _, v := range val {
			maps := map[string]map[string]interface{}{
				"term": {docField: v},
			}
			e.term = append(e.term, maps)
		}
	}
	if val, ok := value.([]int); ok {
		for _, v := range val {
			maps := map[string]map[string]interface{}{
				"term": {docField: v},
			}
			e.term = append(e.term, maps)
		}
	}
	return e
}

// 文档字段值数组包含过滤
// @param  string      docField 字段名
// @param  interface{} value    数组,查找多个包含值，使用数组形式 []int or []string
// @return this
func (e *ElasticClient) SetFilterIn(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"terms": {docField: value},
	}
	if len(e.terms) == 0 {
		e.terms = make([]map[string]map[string]interface{}, 0)
		e.terms = append(e.terms, maps)
	} else {
		e.terms = append(e.terms, maps)
	}
	return e
}

// 文档字段值精确值排除筛选
// @param  string      docField 字段名
// @param  int         value    字段值
// @return this
func (e *ElasticClient) SetFilterFalse(docField string, value int) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"term": {docField: value},
	}
	if len(e.mustNot) == 0 {
		e.mustNot = make([]map[string]map[string]interface{}, 0)
		e.mustNot = append(e.mustNot, maps)
	} else {
		e.mustNot = append(e.mustNot, maps)
	}
	return e
}

// 文档字段筛选 大于
func (e *ElasticClient) SetFilterGt(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			"gt": value,
		}},
	}
	if len(e.ranges) == 0 {
		e.ranges = make([]map[string]map[string]interface{}, 0)
		e.ranges = append(e.ranges, maps)
	} else {
		e.ranges = append(e.ranges, maps)
	}
	return e
}

// 文档字段筛选 大于等于
func (e *ElasticClient) SetFilterGte(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			"gte": value,
		}},
	}
	if len(e.ranges) == 0 {
		e.ranges = make([]map[string]map[string]interface{}, 0)
		e.ranges = append(e.ranges, maps)
	} else {
		e.ranges = append(e.ranges, maps)
	}
	return e
}

// 文档字段筛选 小于
func (e *ElasticClient) SetFilterLt(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			"lt": value,
		}},
	}
	if len(e.ranges) == 0 {
		e.ranges = make([]map[string]map[string]interface{}, 0)
		e.ranges = append(e.ranges, maps)
	} else {
		e.ranges = append(e.ranges, maps)
	}
	return e
}

// 文档字段筛选 小于等于
func (e *ElasticClient) SetFilterLte(docField string, value interface{}) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			"lte": value,
		}},
	}
	if len(e.ranges) == 0 {
		e.ranges = make([]map[string]map[string]interface{}, 0)
		e.ranges = append(e.ranges, maps)
	} else {
		e.ranges = append(e.ranges, maps)
	}
	return e
}

// 文档字段值范围筛选
// @param  string  docField            文档字段名称
// @param  interface{} minValue        字段值范围开始
// @param  interface{}  maxValue       字段值范围结束
// @param  ...interface{} params       可变参数 左边界【gt:大于、gte:大于等于】 右边界【lt:小于、lte:小于等于】
// @return this
func (e *ElasticClient) SetFilterRange(docField string, minV interface{}, maxV interface{}, params ...interface{}) *ElasticClient {
	var leftLimiter string
	var rightLimter string
	if len(params) == 0 {
		leftLimiter = "gte"
		rightLimter = "lte"
	} else {
		if len(params) == 1 {
			leftLimiter = params[0].(string)
			rightLimter = "lte"
		} else {
			leftLimiter = params[0].(string)
			rightLimter = params[1].(string)
		}
	}
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			leftLimiter: minV,
			rightLimter: maxV,
		}},
	}
	if len(e.ranges) == 0 {
		e.ranges = make([]map[string]map[string]interface{}, 0)
		e.ranges = append(e.ranges, maps)
	} else {
		e.ranges = append(e.ranges, maps)
	}
	return e
}

// 文档字段值范围筛选不在范围内
// @param  string  docField            文档字段名称
// @param  interface{} minValue        字段值范围开始
// @param  interface{}  maxValue       字段值范围结束
// @param  ...interface{} params       可变参数 左边界【gt:大于、gte:大于等于】 右边界【lt:小于、lte:小于等于】
// @return this
func (e *ElasticClient) SetFilterNotRange(docField string, minV interface{}, maxV interface{}, params ...interface{}) *ElasticClient {
	var leftLimiter string
	var rightLimter string
	if len(params) == 0 {
		leftLimiter = "gte"
		rightLimter = "lte"
	} else {
		if len(params) == 1 {
			leftLimiter = params[0].(string)
			rightLimter = "lte"
		} else {
			leftLimiter = params[0].(string)
			rightLimter = params[1].(string)
		}
	}
	maps := map[string]map[string]interface{}{
		"range": {docField: map[string]interface{}{
			leftLimiter: minV,
			rightLimter: maxV,
		}},
	}
	if len(e.mustNot) == 0 {
		e.mustNot = make([]map[string]map[string]interface{}, 0)
		e.mustNot = append(e.mustNot, maps)
	} else {
		e.mustNot = append(e.mustNot, maps)
	}
	return e
}

// 设置排序方式
// @param  string     docField 文档排序字段
// @param  ...string  sort 排序模式 【desc:降序、asc:升序 默认=desc】
// @retutn this
func (e *ElasticClient) SetSortMode(docField string, sort ...string) *ElasticClient {
	var sortMode string
	if len(sort) == 0 {
		sortMode = "desc"
	} else {
		sortMode = sort[0]
	}
	maps := map[string]interface{}{
		docField: map[string]string{"order": sortMode},
	}
	if len(e.sort) == 0 {
		e.sort = make([]map[string]interface{}, 0)
		e.sort = append(e.sort, maps)
		e.sort = append(e.sort, map[string]interface{}{"_score": map[string]string{"order": "desc"}})
	} else {
		e.sort = append(e.sort, maps)
	}
	return e
}

// 设置离我最近排序
// @param string   string     索引经纬度字段名
// @param float64   lon     经度
// @param float64   lat     纬度
// @param ...string params  可变参数、接收一个参数，排序单位，默认=m
func (e *ElasticClient) SetNearMode(locationField string, lon float64, lat float64, params ...string) *ElasticClient {
	var unit string
	if len(unit) == 0 {
		unit = "m"
	} else {
		unit = params[0]
	}
	sortMap := map[string]interface{}{
		"_geo_distance": map[string]interface{}{
			locationField:   map[string]interface{}{"lon": lon, "lat": lat},
			"order":         "asc",
			"unit":          unit,
			"distance_type": "plane",
		},
	}
	if len(e.sort) == 0 {
		e.sort = make([]map[string]interface{}, 0)
		e.sort = append(e.sort, sortMap)
	} else {
		e.sort = append(e.sort, sortMap)
	}
	return e
}

// 根据经纬度获取附近指定公里数的矩形四点坐标
// @param float64 lon  经度
// @param float64 lat  维度
// @param float64 distance 公里数
func (e *ElasticClient) SquarePoint(lon float64, lat float64, distance float64) map[string]map[string]float64 {
	var earthRadius float64 = 6371 // 单位千米
	temp := math.Pi
	deg2rad := lat * temp / 180
	var dap float64 = 2
	dlon := dap * math.Asin(math.Sin(distance/(dap*earthRadius))/math.Cos(deg2rad))
	dlon = dlon * (180 / temp)
	dlat := distance / earthRadius
	dlat = dlat * (180 / temp)
	maps := make(map[string]map[string]float64)
	maps["top_left"] = map[string]float64{"lng": lon - dlon, "lat": lat + dlat}
	maps["top_right"] = map[string]float64{"lng": lon + dlon, "lat": lat + dlat}
	maps["bottom_left"] = map[string]float64{"lng": lon - dlon, "lat": lat - dlat}
	maps["bottom_right"] = map[string]float64{"lng": lon + dlon, "lat": lat - dlat}
	return maps
}

// 设置用户定位经纬度以及附近多少公里
// @param string   string     索引经纬度字段名
// @param float64    lon 经度
// @param float64    lat 纬度
// return this
func (e *ElasticClient) SetDistance(locationField string, lon float64, lat float64, distance float64) *ElasticClient {
	squarePoint := e.SquarePoint(lon, lat, distance)
	maps := map[string]interface{}{
		"geo_bounding_box": map[string]interface{}{
			"type": "indexed",
			locationField: map[string]interface{}{
				"top_left": map[string]interface{}{
					"lon": squarePoint["top_left"]["lng"],
					"lat": squarePoint["top_left"]["lat"],
				},
				"bottom_right": map[string]interface{}{
					"lon": squarePoint["bottom_right"]["lng"],
					"lat": squarePoint["bottom_right"]["lat"],
				},
			},
		},
	}
	e.geo = maps
	return e
}

// 设置搜索结果受欢迎程度排序,如调用该方法，则e.SetSortMode()方法就不需要用了
// @param  string docField     影响受欢迎排序字段 如点赞数字段、阅读量字段
// @param  ...string params    可变参数,包含(modifier,factor,boostMode)一种融入受欢迎度更好方式用 modifier 平滑 docField 的值
// 介绍参考：
// https://www.elastic.co/guide/cn/elasticsearch/guide/current/boosting-by-popularity.html
// @return this
func (e *ElasticClient) SetFactor(docField string, params ...string) *ElasticClient {
	var modifier, factor, boostMode string
	if len(params) > 0 {
		modifier = params[0]
		factor = params[1]
		boostMode = params[2]
	} else {
		modifier = "log1p"
		factor = "0.1"
		boostMode = "sum"
	}
	e.factor = make(map[string]interface{})
	e.factor = map[string]interface{}{
		"factor": map[string]string{
			"field":    docField,
			"modifier": modifier,
			"factor":   factor,
		},
		"boost_mode": boostMode,
	}
	return e
}

// 设置分页
// @param  int start
// @param  int limit
// @return this
func (e *ElasticClient) SetLimit(start int, limit int) *ElasticClient {
	searchNum := start * limit
	if searchNum > maxSearchNum {
		start = start - 1
	}
	e.limit = make(map[string]int)
	e.limit["from"] = start
	e.limit["size"] = limit
	return e
}

func (e *ElasticClient) getSearch() (map[string]map[string]map[string]map[string]map[string]interface{}, bool) {
	// 组合map过滤
	search := make(map[string]map[string]map[string]map[string]map[string]interface{})
	search["query"] = make(map[string]map[string]map[string]map[string]interface{})
	search["query"]["function_score"] = make(map[string]map[string]map[string]interface{})
	search["query"]["function_score"]["query"] = make(map[string]map[string]interface{})
	search["query"]["function_score"]["query"]["bool"] = make(map[string]interface{})
	search["query"]["function_score"]["query"]["bool"]["must"] = make([][]map[string]map[string]interface{}, 0)
	search["query"]["function_score"]["query"]["bool"]["must_not"] = make([][]map[string]map[string]interface{}, 0)
	search["query"]["function_score"]["query"]["bool"]["should"] = make([][]map[string]map[string]interface{}, 0)

	var capBool bool
	// 精确值条件
	if len(e.term) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["must"] = append(search["query"]["function_score"]["query"]["bool"]["must"].([][]map[string]map[string]interface{}), e.term)
	}
	// 范围in条件
	if len(e.terms) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["must"] = append(search["query"]["function_score"]["query"]["bool"]["must"].([][]map[string]map[string]interface{}), e.terms)
	}
	// 精确值排除条件
	if len(e.mustNot) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["must_not"] = append(search["query"]["function_score"]["query"]["bool"]["must_not"].([][]map[string]map[string]interface{}), e.mustNot)
	}
	// 范围条件
	if len(e.ranges) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["must"] = append(search["query"]["function_score"]["query"]["bool"]["must"].([][]map[string]map[string]interface{}), e.ranges)
	}
	// 地理位置，筛选附近公里数内的数据
	if len(e.geo) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["filter"] = map[string]interface{}{
			"geo_bounding_box": e.geo["geo_bounding_box"],
		}
	}
	if len(e.should) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["should"] = append(search["query"]["function_score"]["query"]["bool"]["should"].([][]map[string]map[string]interface{}), e.should)
	}
	if len(e.keyword) > 0 {
		capBool = true
		search["query"]["function_score"]["query"]["bool"]["must"] = append(search["query"]["function_score"]["query"]["bool"]["must"].([][]map[string]map[string]interface{}), e.keyword)
	}
	return search, capBool
}

// 字符串过滤
func (e *ElasticClient) Search(keyword string, docField []string) *ElasticClient {
	maps := map[string]map[string]interface{}{
		"query_string": {
			"query":  strings.TrimSpace(keyword),
			"fields": docField,
		},
	}
	if len(e.keyword) == 0 {
		e.keyword = make([]map[string]map[string]interface{}, 0)
		e.keyword = append(e.keyword, maps)
	} else {
		e.keyword = append(e.keyword, maps)
	}
	return e
}

// 执行查询
func (e *ElasticClient) Query() map[string]interface{} {
	search, capBool := e.getSearch()
	requestBody := make(map[string]interface{})
	if capBool {
		for k, v := range search {
			bools, b := search[k]["function_score"]["query"]["bool"]
			if b {
				for ks, vs := range bools {
					if len(vs.([][]map[string]map[string]interface{})) == 0 {
						delete(bools, ks)
					}
				}
			}
			if len(bools) == 0 {
				delete(search[k]["function_score"]["query"], "bool")
			}
			requestBody[k] = v
		}
	}
	// 设置排序
	if len(e.sort) > 0 {
		sort := map[string]interface{}{"sort": e.sort}
		for k, v := range sort {
			requestBody[k] = v
		}
	}
	// 设置分页
	if len(e.limit) > 0 {
		for k, v := range e.limit {
			requestBody[k] = v
		}
	}
	jsonBody, _ := json.Marshal(requestBody)
	e.lastQuery = string(jsonBody[:])
	req := esapi.SearchRequest{
		Index:        []string{e.index},
		DocumentType: []string{e.indexType},
		Body:         bytes.NewReader(jsonBody),
	}
	res, err := req.Do(context.Background(), e.client)
	if err != nil {
		log.Println(err)
		return make(map[string]interface{})
	}
	e.response = res
	defer e.close()
	var maps = make(map[string]map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	_ = json.Unmarshal([]byte(responseStr), &maps)
	var resMap = make(map[string]interface{})
	if len(maps) == 0 {
		return resMap
	}
	if value, ok := maps["hits"]["hits"]; ok {
		resMap["result"] = value
	}
	if value, ok := maps["hits"]["total"]; ok {
		resMap["total"] = value
	}
	return resMap
}

// 打印请求json
func (e *ElasticClient) GetLastQuery() {
	if e.lastQuery != "" {
		fmt.Println("PrintRequestJson:", e.lastQuery)
	} else {
		fmt.Println("PrintRequestJson:empty")
	}
}

// 文档是否存在
func (e *ElasticClient) DocExist(docId string) bool {
	res, _ := e.client.Exists(e.index, docId)
	if res.StatusCode == 200 {
		return true
	}
	return false
}

//嵌套文档，根据文档id新增嵌套字段数据
func (e *ElasticClient) AddNestedDataByDocId(docId string, nestedField string, data map[string]interface{}) bool {
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"source": "ctx._source." + nestedField + ".add(params.data)",
			"params": map[string]interface{}{
				"data": data,
			},
		},
	}
	jsonBody, _ := json.Marshal(body)
	res, err := e.client.Update(e.index, docId, bytes.NewReader(jsonBody))
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//嵌套文档，根据文档id修改嵌套字段子文档数据
//@parmas docId 文档id
//@params nestedField 嵌套文档字段名称
//@params nestedDocIdField 要修改的嵌套文档中子文档id名称
//@params updateNestedFields 要修改的字段名称
//@params data 要修改的数据
func (e *ElasticClient) EditNestedDataByDocId(docId string, nestedField string, nestedDocIdField string, updateNestedFields []string, data map[string]interface{}) bool {
	updateStr := ""
	for _, field := range updateNestedFields {
		updateStr = updateStr + fmt.Sprintf("e.%s=params.%s;", field, field)
	}
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"source": "for(e in ctx._source." + nestedField + "){if (e." + nestedDocIdField + " == params." + nestedDocIdField + ") {" + updateStr + "}}",
			"params": data,
		},
	}
	jsonBody, _ := json.Marshal(body)
	res, err := e.client.Update(e.index, docId, bytes.NewReader(jsonBody))
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//嵌套文档，根据文档id修改嵌套字段的嵌套文档数据
//@parmas docId 	  文档id
//@params nestedField 嵌套文档字段名称
//@params nestedDocIdField 要修改的嵌套文档中的文档id名称
//@params nestedDocId 要修改的嵌套文档中的文档id
func (e *ElasticClient) DelNestedDataByDocId(docId string, nestedField string, nestedDocIdField string, nestedDocId int) bool {
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"source": "ctx._source." + nestedField + ".removeIf(it -> it." + nestedDocIdField + " == params." + nestedDocIdField + ");",
			"params": map[string]interface{}{
				nestedDocIdField: nestedDocId,
			},
		},
	}
	jsonBody, _ := json.Marshal(body)
	fmt.Println(string(jsonBody))
	res, err := e.client.Update(e.index, docId, bytes.NewReader(jsonBody))
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//嵌套文档字符串过滤
//@params keyword 搜索关键词
//@params mainDocField 主文档查询字段
//@params nestedDocField 嵌套子文档查询字段
//@params path 嵌套子文档名称
//@params innerHits 是否返回关联的嵌套文档数据 true=返回 false=不返回
//@params innerHitsLimit 返回的嵌套关联文档的数量
func (e *ElasticClient) NestedShouldSearch(keyword string, mainDocField []string, nestedDocField []string, path string, innerHits bool, innerHitsLimit int) *ElasticClient {
	if len(e.term) == 0 {
		e.term = make([]map[string]map[string]interface{}, 0)
	}

	condition := []map[string]map[string]interface{}{}
	mainMaps := map[string]map[string]interface{}{}
	if len(mainDocField) > 0 && len(keyword) > 0 {
		mainMaps = map[string]map[string]interface{}{
			"query_string": {
				"query":  strings.TrimSpace(keyword),
				"fields": mainDocField,
			},
		}
		condition = append(condition, mainMaps)
	}

	maps := map[string]map[string]interface{}{}
	if len(keyword) > 0 {
		maps["nested"] = map[string]interface{}{
			"path": path,
			"query": map[string]interface{}{
				"query_string": map[string]interface{}{
					"query":  strings.TrimSpace(keyword),
					"fields": nestedDocField,
				},
			},
		}
	} else {
		maps["nested"] = map[string]interface{}{
			"path": path,
			"query": map[string]interface{}{
				"match_all": map[string]interface{}{},
			},
		}
	}

	if innerHits == true {
		maps["nested"]["inner_hits"] = map[string]int{}
		if innerHitsLimit > 0 {
			maps["nested"]["inner_hits"] = map[string]int{
				"from": 0,
				"size": innerHitsLimit,
			}
		}
	}
	condition = append(condition, maps)
	must := map[string]map[string]interface{}{}
	must["bool"] = make(map[string]interface{})
	must["bool"]["should"] = condition

	e.term = append(e.term, must)
	return e
}

//根据文档id 向数组里追加元素
//@parmas docId 文档id
//@params arrField 数组类型文档字段名称
//@params data 要修改的数据
func (e *ElasticClient) AddArrayDataByDocId(docId string, arrField string, data interface{}) bool {
	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"source": "ctx._source." + arrField + ".add(params.data)",
			"params": map[string]interface{}{
				"data": data,
			},
		},
	}
	jsonBody, _ := json.Marshal(body)
	res, err := e.client.Update(e.index, docId, bytes.NewReader(jsonBody))
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//批量操作es
func (e *ElasticClient) AddDataByBulk(body io.Reader) bool {
	res, err := e.client.Bulk(body)
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//根据文档ids集合 向数组里追加元素
//@parmas docIds 文档ids集合
//@params arrField 数组类型文档字段名称
//@params data 要修改的数据
func (e *ElasticClient) AddArrayDataByDocIds(docIds []int, arrField string, data interface{}) bool {
	if len(docIds) <= 1 {
		return false
	}

	var should = make([]map[string]map[string]interface{}, 0, len(docIds))
	var match map[string]map[string]interface{}
	var item map[string]interface{}
	for _, docId := range docIds {
		item = map[string]interface{}{"_id": strconv.Itoa(docId)}
		match = map[string]map[string]interface{}{"match": item}
		should = append(should, match)
	}

	body := map[string]interface{}{
		"script": map[string]interface{}{
			"lang":   "painless",
			"source": "ctx._source." + arrField + ".add(params.data)",
			"params": map[string]interface{}{
				"data": data,
			},
		},
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"should": should,
			},
		},
	}
	fmt.Println(body)
	jsonBody, _ := json.Marshal(body)
	req := esapi.UpdateByQueryRequest{
		Index:        []string{e.index},
		DocumentType: []string{e.indexType},
		Body:         bytes.NewReader(jsonBody),
	}
	res, err := req.Do(context.Background(), e.client)
	defer e.close()
	if err != nil {
		fmt.Println("req.Do Error,Error is ", err.Error())
		return false
	}
	e.response = res
	var maps = make(map[string]interface{})
	responseStr := res.String()[strings.Index(res.String(), "{"):]
	err = json.Unmarshal([]byte(responseStr), &maps)
	if _, ok := maps["error"]; ok {
		fmt.Println("req.Do Error,Error is", maps["error"])
		return false
	} else {
		return true
	}
}

//根据内嵌path和field的值查询文档
//@parmas path 内嵌类型的路径
//@params field 字段
//@params value 值   //Single.ItemId = 11
func (e *ElasticClient) GetByNestedByPathField(path, field string, value int) {
	item := map[string]interface{}{
		"path": path,
		"query": map[string]interface{}{
			"term": map[string]interface{}{
				fmt.Sprintf("%v.%v", path, field): map[string]interface{}{
					"value": value,
				},
			},
		},
	}
	nested := make(map[string]map[string]interface{})
	nested["nested"] = item
	e.terms = append(e.terms, nested)
}

//根据传入的切片 为每个值 匹配 或操作满足 切片其中一个值就可以命中
//@parmas  string  docField 字段名
//@parmas interface{} value  数组，命中数组任意一个元素即可，使用数组形式 []int or []string
func (e *ElasticClient) SetShouldTerm(docField string, value interface{}) {
	if len(e.term) == 0 {
		e.term = make([]map[string]map[string]interface{}, 0)
	}
	var terms []map[string]map[string]interface{}
	if val, ok := value.([]string); ok {
		for _, v := range val {
			terms = append(terms, map[string]map[string]interface{}{
				"term": {docField: v},
			})
		}
	}
	if val, ok := value.([]int); ok {
		for _, v := range val {
			terms = append(terms, map[string]map[string]interface{}{
				"term": {docField: v},
			})
		}
	}
	m := map[string]map[string]interface{}{
		"bool": {"should": terms},
	}

	e.term = append(e.term, m)
}

// 设置排序方式
// @param  string     docField 文档排序字段
// @param  ...string  sort 排序模式 【desc:降序、asc:升序 默认=desc】
// @retutn this
func (e *ElasticClient) SetSortMode2(docField string, sort ...string) *ElasticClient {
	var sortMode string
	if len(sort) == 0 {
		sortMode = "desc"
	} else {
		sortMode = sort[0]
	}
	maps := map[string]interface{}{
		docField: map[string]string{"order": sortMode},
	}
	e.sort = append(e.sort, maps)
	return e
}

// 实例化引用
func NewElasiticClient(index string) (*ElasticClient, error) {
	var err error
	if indexPre == "" {
		indexPre = indexPrefix
	}
	return &ElasticClient{
		index:     indexPre + index,
		indexType: "_doc",
		client:    client,
	}, err
}

// 清除查询缓存条件
func (e *ElasticClient) ClearSearch() *ElasticClient {
	e.term = nil
	e.terms = nil
	e.mustNot = nil
	e.ranges = nil
	e.keyword = nil
	e.sort = nil
	e.limit = nil
	e.geo = nil
	e.factor = nil
	return e
}

// 设置索引
func (e *ElasticClient) SetIndex(index string) *ElasticClient {
	indexPre := kcgin.AppConfig.String("es.indexpre")
	if indexPre == "" {
		indexPre = indexPrefix
	}
	e.index = indexPre + index
	return e
}

// 关闭
func (e *ElasticClient) close() {
	_err := e.response.Body.Close()
	if _err != nil {
		fmt.Println("close Error,Error is ", _err.Error())
	}
}
